原生js模拟实现ajax(不兼容IE5/6)

AJAX:Asynchronous JavaScript and XML(异步的 JavaScript 和 XML),可以在不重新加载整个网页的情况下实现异步更新。
XMLHttpRequest:XMLHttpRequest 得到了所有现代浏览器较好的支持(在 IE 5 和 IE 6 中,必须使用特定于 IE 的 ActiveXObject),参考MDN了解更多

1. 简单后端

使用一个php文件来模拟服务器,实现对提交的2个数字相加后返回数据,php代码如下:

<?php
echo $_GET['a']+$_GET['b'];
?>

2. XMLHttpRequest请求过程

请求的发送实际上底层都通过浏览器的XMLHttpRequest实现(ie5、ie6使用ActiveXObject),不考虑兼容,我们实现一个简单的XMLHttpRequest请求流程如下:

  1. 初始化对象
    let xhr=new XMLHttpRequest(); // 不兼容ie6
  2. 连接:true代表异步,false代表同步
    xhr.open('get','../server/a.php',true,);
  3. 发送请求(post方式时,send里面是body,post需要发送header)
    xhr.send();
  4. 接收(需要判断什么时候是接收完成)
    xhr.onreadystatechange = function(){};

3.GET和POST

举例需要提交数据a=5、b=12,我们知道get在header传数据,post在body里传数据。

GET
xhr.open('get','../server/a.php?a=5&b=12',true,);
xhr.send();
POST

按照上面代码的思路,post请求的写法推导出来应该是如下写法:

xhr.open('post','../server/a.php',true,);
// 发送;send里面是body,post需要发送
xhr.send('a=12&b=5');

实际上如上写法并不行!
对比了form的post提交方式,可以看到,Request Headers里面有条设置和form提交的不同:

xhr发送的Content-Type: text/plain;charset=UTF-8
form提交的Content-Type: application/x-www-form-urlencoded

POST中Content-Type类型及含义?

text/plain 文本
application/x-www-form-urlencoded &&&连接的方式,eg:a=12&b=5
multippart/form-data 定界符分割各个数据(例如文件上传)

a=12&b=5属于application/x-www-form-urlencoded内容方式,因此,需要增加setRequestHeader的设置,再send请求的body内容,请求成功。

xhr.open('post','../server/a.php',true,);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send('a=12&b=5');

4. 判断什么时候接收完成

分析XMLHttpRequest和Http状态
xhr.readyState状态值

0:刚刚创建初始状态
1:已连接
2:已发送
3:已接受-头(32k上限)
4:已接受-body(1G上限)

http状态码

1XX 消息
2XX 成功
3XX 重定向
    301 永久重定向——浏览器永远不会再次请求老的地址
    302 临时重定向——浏览器下次还会请求老地址
    304 (not modified)重定向到缓存请求——因此304也是成功
4XX 请求错误,客户端错误
5XX 服务端错误
6XX 扩展错误码

因此步骤4中,可以通过XMLHttpRequest状态码为4,Http状态码为2XX和304判断请求成功。

    xhr.onreadystatechange = function(){
        if (xhr.readyState ==4){
            if ((xhr.status>=200&&xhr.status<300)||xhr.status==304){
                alert('成功:'+xhr.responseText);
                console.log(xhr);
            } else {
                alert('失败');
            }
        }
    };

5. 代码实现ajax

function ajax(options){
    // 数据处理
    options = options || {};
    options.data = options.data || {};
    options.type = options.type || 'get';
    options.dataType = options.dataType || 'text'; //解析数据

    let arr = [];
    for (let name in options.data) {
        arr.push(`${name}=${encodeURIComponent(options.data[name])}`);
    }
    let dataStr = arr.join('&');

    // 不兼容ie6/5
    let xhr=new XMLHttpRequest();

    // 连接,true代表异步,false代表同步;浏览器对异步的xhr会报错
    if (options.type == 'get'){
        xhr.open('get',options.url + '?' + dataStr,true);
        xhr.send();
    } else {
        xhr.open('post',options.url,true,);

        // 发送;send里面是body,post需要发送Content-Type
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
        xhr.send(dataStr);
    }

    // 接收;4代表结束
    xhr.onreadystatechange = function(){
        if (xhr.readyState ==4){
            if ((xhr.status>=200&&xhr.status<300)||xhr.status==304){
                let result = xhr.responseText;
                switch (options.dataType){
                    case 'text':
                        break;
                    case 'json':
                        if (window.JSON && JSON.parse){
                            result = JSON.parse(result);
                        } else {
                            result = eval("("+result+")");
                        }
                        break;
                    case 'xml':
                        result = xhr.XMLHttpRequest;
                        break;
                    default:
                        break;

                }
                options.success && options.success(result);
            } else {
                options.error && options.error('error');
            }
        }
    };
}

jsonp简单使用

由于安全原因(过于开放),使用在减少,可以用来跨域
以百度的一个请求举例,说明如何使用jsonp:
clipboard.png

https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=s&json=1&p=3&sid=1458_25809_26911_21115_26350_26924_20719&req=2&csor=1&cb=jQuery11020985645405885194_1532486329485&_=1532486329488

wd:请求关键字
cb:callback,回调方法

请求得到的数据结构如下:image.png

q:查询字段
s:相关结果

1. 原理

准备一个回调函数等着接口回调,回调的的时候传参,进行数据传递从而实现跨域。

Q: 为什么cb指定的是回调函数,wd是请求关键字?
是由后台指定的,也可以是其他的参数,服务器和后台约定好即可. 这里百度正好定义的是wd和cb.

2. 用法示例

2.1 本地调用
根据参数形式,我们简单实现本地引用回调show函数来进行演示
baidu_data.js文件

show({q:"s",p:false,s:["世界杯","世界杯赛程","顺丰快递单号查询","圣墟","三国杀","双色球","搜狗输入法","搜狗","上海天气","生死狙击"]});

jsonp.html文件

<script>
    function show(params){
        alert(params.s);
    }
</script>
<script charset="utf-8" src="./baidu_data.js"></script>

能够成功回调,将baidu_data.js的数据显示出来。
clipboard.png

2.2 改造百度url的回调方法
改造百度url的回调方法为show,将百度查询结果使用列表显示在界面上。

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript">
        let url = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=&cb=show";
        function show(params) {
            alert(params.s);
        }
    </script>
</head>
<body>
    <input type="text" id="txt1">
    <ul id="ul"></ul>
</body>
</html>

访问url为:https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=s&cb=show,可以看到url调用对应本地js代码,是一个show函数调用,访问得到如下结果。
clipboard.png

2.3 百度url本地化
结合1、2,将本地引用换成我们改造的百度的url:https://sp0.baidu.com/5a1Fazu...,再直接访问该html文件,是和上面一样的结果。

<script>
    function show(params){
        alert(params.s);
    }
</script>
<script charset="utf-8" src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=s&cb=show"></script>

2.4 完整示例
script是一次性标签,src只能赋值一次,再赋值不会被改变;以下示例每次移除老的script,新建一个script标签用于动态获取数据.

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript">
        let url = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=&cb=show";
        function show(json) {
            let oUl = document.getElementById('ul');
            oUl.innerHTML = '';
            // 将结果显示在列表中
            json.s.forEach(item=>{
                let oLi = document.createElement('li');
                oLi.innerHTML = item;
                oUl.appendChild(oLi);
            });
        }
        window.onload=function(){
            let oTxt = document.getElementById('txt1');
            oTxt.oninput = function(){
                // 移除老的用于获取数据的script标签
                let oldScript = document.getElementById('dynamic-script');
                if (oldScript){
                    document.head.removeChild(oldScript);
                }
                // 每次新建一个新的script标签用于更新src
                let newScript = document.createElement('script');
                newScript.id = 'dynamic-script'
                newScript.src =  `https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=${encodeURIComponent(oTxt.value)}&cb=show`;
                document.head.appendChild(newScript);
            }
        }
    </script>
</head>
<body>
    <input type="text" id="txt1">
    <ul id="ul"></ul>
</body>
</html>

小叶子
69 声望11 粉丝

一只萌萌的程序媛妹子,踏入前端领域不久,希望大家多指导,欢迎大家多多指出问题